#include "neli.h"
#include "common.h"
#include "init.h"
#include <linux/net.h>
#include <net/genetlink.h>

static struct nla_policy counter_genl_policy[SYNC_COUNTER_A_MAX + 1] = {
	[SYNC_COUNTER_A_MSG] = { .type = NLA_NUL_STRING }
};

static struct genl_multicast_group notify_group = {
	.name = SYNC_COUNTER_NOTIFY_GROUP
};

static struct genl_ops counter_ops[__SYNC_COUNTER_C_MAX] = {
	{
		.cmd = SYNC_COUNTER_C_START,
		.flags = 0,
		.policy = NULL,
		.doit = NULL,
		.dumpit = NULL,
	},

	{
		.cmd = SYNC_COUNTER_C_STOP,
		.flags = 0,
		.policy = NULL,
		.doit = NULL,
		.dumpit = NULL,
	},
};

static struct genl_family counter_family = {
	.hdrsize = 0,
	.version = 1,
	.maxattr = SYNC_COUNTER_A_MAX,
	.policy = counter_genl_policy,
	.n_ops = 2,
	.n_mcgrps = 1,
	.mcgrps = &notify_group,
	.ops = counter_ops,
};

static int init_netlink_family(struct init_block *init);
static void cleanup_netlink_family(struct init_block *init);

struct netlink_init_cfg {
	struct init_block init;
	const char *family_name;
} neli_init_cfg = {
	.init = {
		.name = "sync-counter",
		.init_fn = init_netlink_family,
		.cleanup_fn = cleanup_netlink_family,
		.drop_fn = NULL,
	},
};

int setup_netlink(
	const char *family_name,
	genl_start_handler_t start_fn,
	genl_stop_handler_t stop_fn
)
{
	PR_INFO("setup netlink family by name: %s", family_name);
	neli_init_cfg.family_name = family_name;
	counter_ops[SYNC_COUNTER_C_START].doit = start_fn;
	counter_ops[SYNC_COUNTER_C_STOP].doit = stop_fn;
	return try_init(&neli_init_cfg.init);
}

static int init_netlink_family(struct init_block *init)
{

	int ret = 0;
	struct netlink_init_cfg *cfg = container_of(init, struct netlink_init_cfg, init);
	counter_family.module = THIS_MODULE;
	strncpy(counter_family.name, cfg->family_name, GENL_NAMSIZ);
	PR_INFO("netlink family name: %s", cfg->family_name);
	ret = genl_register_family(&counter_family);
	if (ret) {
		PR_ERR("register genl family failed, errno: %d", ret);
		counter_family.id = 0;
	} else {
		PR_INFO("register genl family success, family id: %d", counter_family.id);
	}

	return ret;
}

static void cleanup_netlink_family(struct init_block *init)
{
	int ret = 0;
	if (counter_family.id != 0) {
		PR_INFO("unregister genl counter_family");
		ret = genl_unregister_family(&counter_family);
		if (ret) {
			PR_ERR("unregister genl counter_family failed, errno: %d\n", ret);
		}
		PR_INFO("finish unregister genl counter_family");
	}
}

static int boardcast_signal(int signal, int seq) 
{
	int ret = 0;
	int pid = 0;
	// int seq = signal;
	void *msg_head = NULL;
	struct sk_buff *skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
	if (skb == NULL) {
		PR_ERR("genlmsg_new failed");
		return -ENOMEM;
	}

	msg_head = genlmsg_put(skb, pid, seq, &counter_family, 0, signal);
	if (msg_head == NULL) {
		PR_ERR("genlmsg_put failed");
		kfree_skb(skb);
		return -ENOMEM;
	}

	ret = genlmsg_multicast(&counter_family, skb, 0, 0, GFP_KERNEL);
	return ret;
}

int boardcast_signal_start(int seq) {
	PR_INFO("boardcast signal start: %d", SYNC_COUNTER_C_START);
	return boardcast_signal(SYNC_COUNTER_C_START, seq);
}

int boardcast_signal_stop(void) {
	PR_INFO("boardcast signal stop: %d", SYNC_COUNTER_C_STOP);
	return boardcast_signal(SYNC_COUNTER_C_STOP, SYNC_COUNTER_C_STOP);
}

